package com.rlj.messagequeue.mqserver.datacenter;

import com.rlj.messagequeue.common.BinaryTool;
import com.rlj.messagequeue.common.MqException;
import com.rlj.messagequeue.mqserver.core.MSGQueue;
import com.rlj.messagequeue.mqserver.core.Message;

import java.io.*;
import java.util.Scanner;

/**
 * Created with IntelliJ IDEA.
 * Description: 消息的文件操作类
 * User: DELL
 * Date: 2024-02-25
 * Time: 22:51
 */
public class MessageFileManager {
    /**
     * 文件的统计信息
     * 主要记录消息总数和有效消息总数
     */
    static public class Stat {
        public int totalCount;
        public int validCount;
    }

    private String getQueueDir(String queueName) {
        return "./data/" + queueName;
    }

    /**
     * 获取数据文件的路径
     *
     * @param queueName
     * @return
     */
    private String getQueueDataPath(String queueName) {
        return getQueueDir(queueName) + "/queue_data.txt";
    }

    /**
     * 获取统计文件的路径
     *
     * @param queueName
     * @return
     */
    private String getQueueStatPath(String queueName) {
        return getQueueDir(queueName) + "/queue_stat.txt";
    }


    /**
     * 读取统计文件的详细信息
     *
     * @param queueName
     * @return
     */
    private Stat readStat(String queueName) {
        Stat stat = new Stat();
        try (InputStream inputStream = new FileInputStream(getQueueStatPath(queueName))) {
            Scanner scanner = new Scanner(inputStream);
            stat.totalCount = scanner.nextInt();
            stat.validCount = scanner.nextInt();
        } catch (IOException e) {
            System.out.println("[MessageFileManager] 读取统计文件失败");
            e.printStackTrace();
        }
        return stat;
    }

    /**
     * 更改统计文件的详细信息
     *
     * @param queueName
     * @param stat
     */
    private void writeStat(String queueName, Stat stat) {
        // 使用 OutputStream 打开文件的时候，默认会把打开的文件清空
        try (OutputStream outputStream = new FileOutputStream(getQueueStatPath(queueName))) {
            PrintWriter printWriter = new PrintWriter(outputStream);
            printWriter.write(stat.totalCount + "\t" + stat.validCount);
            // 刷新缓冲区
            printWriter.flush();
        } catch (IOException e) {
            System.out.println("[MessageFileManager] 更改统计文件失败");
            e.printStackTrace();
        }
    }

    /**
     * 创建队列对应的文件和目录
     *
     * @param queueName
     */
    public void createQueueFiles(String queueName) throws IOException {
        // 1. 创建队列对应的消息目录
        File baseDir = new File(getQueueDir(queueName));
        if (!baseDir.exists()) {
            boolean flag = baseDir.mkdirs();
            if (!flag) {
                throw new IOException("创建目录失败！baseDir=" + baseDir.getAbsoluteFile());
            }
        }

        // 2. 创建队列数据文件
        File dataFile = new File(getQueueDataPath(queueName));
        if (!dataFile.exists()) {
            boolean flag = dataFile.mkdirs();
            if (!flag) {
                throw new IOException("创建文件失败！dataFile=" + dataFile.getAbsoluteFile());
            }
        }

        // 3. 创建消息统计文件
        File statFile = new File(getQueueStatPath(queueName));
        if (!statFile.exists()) {
            boolean flag = statFile.mkdirs();
            if (!flag) {
                throw new IOException("创建文件失败！statFile=" + statFile.getAbsoluteFile());
            }
        }

        // 4. 给消息统计文件设初值
        Stat stat = new Stat();
        stat.totalCount = 0;
        stat.validCount = 0;
        this.writeStat(queueName, stat);
    }

    /**
     * 删除队列目录和文件
     *
     * @param queueName
     */
    public void deleteQueueFiles(String queueName) throws IOException {
        File dataFile = new File(getQueueDataPath(queueName));
        boolean flag1 = dataFile.delete();
        File statFile = new File(getQueueStatPath(queueName));
        boolean flag2 = statFile.delete();
        File dir = new File(getQueueDir(queueName));
        boolean flag3 = dir.delete();
        if (!flag1 || !flag2 || !flag3) {
            throw new IOException("删除队列目录和文件失败! baseDir=" + dir.getAbsoluteFile());
        }
    }

    /**
     * 检查队列对应的目录和文件是否存在
     *
     * @param queueName
     */
    public boolean checkQueueFileExists(String queueName) {
        File dataFile = new File(getQueueDataPath(queueName));
        File statFile = new File(getQueueStatPath(queueName));
        return dataFile.exists() && statFile.exists();
    }

    /**
     * 将消息序列化为字节数组后，写入队列对应的数据文件中
     *
     * @param queue
     * @param message
     * @throws MqException
     * @throws IOException
     */
    public void sendMessage(MSGQueue queue, Message message) throws MqException, IOException {
        // 1. 检查要写入的文件是否存在
        if (!checkQueueFileExists(queue.getName())) {
            throw new MqException("[MessageFileManager] 队列文件不存在! queueName=" + queue.getName());
        }

        // 2. 获取文件长度
        File dataFile = new File(getQueueDataPath(queue.getName()));
        long byteLen = dataFile.length();

        // 3. 消息序列化
        byte[] messageBytes = BinaryTool.toBytes(message);
        message.setOffsetBeg(byteLen + 4);
        message.setOffsetEnd(byteLen + 4 + messageBytes.length);

        // 4. 消息存入文件
        // 第二个参数传入 true 会以追加的形式打开文件, 不然默认会直接清空源文件
        try (OutputStream outputStream = new FileOutputStream(dataFile, true)) {
            try (DataOutputStream dataOutputStream = new DataOutputStream(outputStream)) {
                // 先用四个字节写入 message 的长度，单位字节
                dataOutputStream.writeInt(messageBytes.length);
                // 后写入 messag e主体
                dataOutputStream.write(messageBytes);
            }
        }

        // 5. 维护统计文件
        Stat stat = readStat(queue.getName());
        stat.totalCount++;
        stat.validCount++;
        writeStat(queue.getName(), stat);
    }
}
